#ifndef OUTPUT_H
#define OUTPUT_H

// #define CLASSIFIER_SHOW
// #define ARMORBOXESDETECTOR_SHOW

#include <string>
#include "opencv2/opencv.hpp"
#include <stdarg.h>
#include <chrono>
#include <functional>
#include <Thread/threadSafeQueue.hpp>
#include <thread>
#include <atomic>
#include <linux/rtc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fstream>
#include <iostream>
#include "socket.hpp"

//#define RECORD_INFO
//#define RECORD_VIDEO
#define MAX_SEND_SIZE 20
extern std::ofstream outfile;
extern Socket sock;
extern Socket rcvsock;
extern bool is_sock_connected;
extern bool is_senddata_updated;
extern float senddata[MAX_SEND_SIZE];

// asynchronous print
class asyncPrinter
{
private:
    ThreadSafeQueue<std::string> printQueue;
    std::atomic_bool is_print_thread_running;
    std::atomic_bool is_print_thread_exited;
    std::thread print_thread;

    void print_thread_proc()
    {
        std::string printstr;
        std::string tmp;
        is_print_thread_exited.store(false);
        int cnt = 0;
        while (is_print_thread_running.load())
        {		
            if (printQueue.try_pop(tmp))
            {
                printstr += tmp;
                cnt++;
                if (cnt > 20)
                {   
                    printf("%s",printstr.c_str());
                    printstr.clear();
                    cnt = 0;
                }
            }
            else
            {
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        }
        is_print_thread_exited.store(true);
    }
public:
    asyncPrinter(size_t max_buffer_size = 100):printQueue(max_buffer_size)
    {
        is_print_thread_running.store(true);
        print_thread = std::thread(&asyncPrinter::print_thread_proc, this);
        print_thread.detach();
    }
    ~asyncPrinter()
    {
        is_print_thread_running.store(true);
        while(!is_print_thread_exited.load())
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    void print(const char * format, ...)
    {
        char ss[200];
        va_list ap;
        va_start(ap, format);
        sprintf(ss, format, ap);
        va_end(ap);
        std::string str(ss);
        printQueue.push(str);
    }
};


class asyncDisplayer
{
public:
    using bufferType = std::pair<std::string, cv::Mat>;
private:
    ThreadSafeQueue<bufferType>displayQueue;
    std::atomic_bool is_display_thread_running;
    std::atomic_bool is_display_thread_exited;
    std::thread display_thread;

    void display_thread_proc()
    {
        bufferType data = make_pair(std::string(),cv::Mat());
        is_display_thread_exited.store(false);
        while (is_display_thread_running.load())
        {
            if(displayQueue.wait_and_pop(data))
            {
                cv::imshow(data.first, data.second);
                cv::waitKey(5);
            }
        }
        is_display_thread_exited.store(true);
    }

public:
    asyncDisplayer(size_t max_buffer_size = 10):displayQueue(max_buffer_size)
    {
        is_display_thread_running.store(true);
        display_thread = std::thread(&asyncDisplayer::display_thread_proc, this);
    }
     ~asyncDisplayer()
    {
        is_display_thread_running.store(true);
        while(!is_display_thread_exited.load())
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    bool display(const std::string & winname, const cv::Mat& img)
    {
        return displayQueue.push(make_pair(winname, img));	
    }
};

class asyncRecorder
{
private:
    ThreadSafeQueue<cv::Mat>recordQueue;
    std::atomic_bool is_record_thread_running;
    std::atomic_bool is_record_thread_exited;
    std::thread record_thread;

    std::string savepath;

    void record_thread_proc()
    {
        cv::Mat image;
        is_record_thread_exited.store(false);
        
        uint64_t saveIdx = 0;
        while(is_record_thread_running.load())
        {
            if(recordQueue.wait_and_pop(image) && !image.empty())
            {
                try
                {
                    cv::imwrite(savepath + std::to_string(saveIdx++)+".jpg",image);
                }
                catch(const std::exception& e)
                {
                    std::cout << e.what() << '\n';
                    printf("record error\n");
                    is_record_thread_running.store(false);
                }
            }
        }
        is_record_thread_exited.store(true);
    }
public:
    static std::string timestr()
    {
        time_t now;
        time(&now);
        struct tm *p;
        p=localtime(&now); /*取得当地时间*/
        char ss[200];
        sprintf(ss, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", (1900+p->tm_year),(1+p->tm_mon), p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
        return std::string(ss);
    }

    asyncRecorder(const std::string& savepath, size_t max_buffer_size = 30):savepath(savepath), recordQueue(max_buffer_size)
    {
        //printf("1");
        is_record_thread_running.store(true);
        //std::cout<<"savepath"<<savepath<<std::endl;
        int res = mkdir(savepath.c_str(), 0777);
        if(res != 0)
        {
            printf("asyncRecorder mkdir error\n");
        }
        //printf("2");
        std::cout<<"savepath"<<savepath<<std::endl;
        record_thread = std::thread(&asyncRecorder::record_thread_proc, this);
    }
     ~asyncRecorder()
    {
        is_record_thread_running.store(true);
        while(!is_record_thread_exited.load())
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    bool record(const cv::Mat& image)
    {
        return recordQueue.push(image);
    }
};
// asynchronous print
void print(const char * format, ...);

// initial 
void init_output();

void stop_output();

// asynchronous display
void display(const std::string & winname, const cv::Mat& img);
#endif
